![]() |
![]() |
|
Der Typ Object bzw. sein C#-Alias object nimmt eine ganz besondere Stellung in der .NET-Klassenbibliothek ein. Alle .NET-Klassen siedeln sich mehr oder weniger tief im Dickicht einer baumartigen Vererbungshierarchie an. Das gilt nicht nur für die Klassen, die in der Klassenbibliothek veröffentlicht werden, das gilt gleichermaßen auch für alle benutzerdefinierten Klassen. So wie sich jeder Ast eines Baumes auf einen Stamm zurückverfolgen lässt, lässt sich auch ausnahmslos jede Klasse auf den Typ Object zurückführen, denn grundsätzlich jede .NET-Klasse wird aus Object abgeleitet – selbst dann, wenn dies nicht explizit angegeben ist. Erinnern Sie sich an die Erläuterung des Begriffs der Vererbung am Anfang dieses Kapitels? Es wurde eine Klasse Luftfahrzeug als Basisklasse angenommen, von der sich die Klassen Starrflügler, Hubschrauber und Zeppelin ableiteten mit dem Ziel, durch die Vererbung Merkmale des allgemeinen Typs Luftfahrzeug an die abgeleiteten Klassen zu vererben. Daraus folgte auch die Erkenntnis, dass ein Starrflügler ein Luftfahrzeug ist, ebenso wie ein Hubschrauber oder ein Zeppelin.
Diese Feststellung können Sie nun abstrakt portieren. Da Object die Basisklasse aller .NET-Klassen ist, müssen alle abgeleiteten Typen gleichzeitig auch immer vom Typ Object sein. Aus diesem Grund kann die Variable myObj vom Typ Object zur Laufzeit auf jeden beliebigen .NET-Typ verweisen. Betrachten Sie das folgende Codefragment. Die Variable meinKreis ist spezifisch deklariert und vom Typ der Klasse Circle, die Variable obj allgemein gehalten und vom Typ Object. Zu einem späteren Zeitpunkt könnte der Variablen obj die Referenz auf das Objekt vom Typ Circle zugewiesen werden:
Selbstverständlich wäre auch das Zuweisen einer Referenz vom Typ ClassA oder ClassB an obj möglich. Im ersten Moment mag es verlockend klingen, alle Objektvariablen vom Typ object zu deklarieren. Diese scheinbare Flexibilität sollte aber dennoch – falls es nicht unumgänglich ist – vermieden werden, da die typgenaue Deklaration einige Vorteile hat:
4.7.3 Mehrere Referenzen auf ein Objekt
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Circle meinKreis = new Circle(); |
| Circle secondObj = meinKreis; |
Zuerst wird eine Variable vom Typ Circle deklariert und initialisiert. Anschließend wird der Verweis auf das Circle-Objekt der Variablen secondObj zugewiesen. Trotz zwei verschiedener Objektreferenzen liegt nur ein konkretes Objekt vor, das allerdings über beide Referenzen angesprochen werden kann.
Weisen Sie einem Feld des Objekts über eine der beiden Referenzen einen Wert zu, beispielsweise mit
| meinKreis.Radius = 10 |
können Sie auch mit der zweiten Objektreferenz den Inhalt der Eigenschaft auswerten:
| Console.WriteLine(secondObj.Radius) |
An der Konsole wird 10 angezeigt, da die Variable secondObj auf dasselbe Objekt verweist wie die Variable meinKreis. Wird ein Objekt mehrfach referenziert, spielt es demnach keine Rolle, über welche Referenz der Eigenschaft ein Wert zugewiesen bzw. ein Feld ausgelesen wird – die Operation wird auf demselben konkreten Objekt ausgeführt.
Eine Mehrfachreferenzierung hat auch noch weitere Konsequenzen: Geben Sie eine der Referenzen mit null frei, wird das Objekt nicht zerstört, denn die zweite, weiterhin gültige Referenz garantiert das Weiterleben des Objekts.
| Circle meinKreis1 = new Circle(); |
| Circle meinKreis2; |
| meinKreis2 = meinKreis1; |
| meinKreis2.Radius = 20; |
| meinKreis2 = null; |
| Console.WriteLine(meinKreis1.Radius); |
An der Konsole wird in diesem Fall immer noch der Inhalt der Eigenschaft Radius ausgegeben. Erst mit der Freigabe der letzten gültigen Referenz auf eine Klasseninstanz wird das Objekt tatsächlich unwiederbringlich freigegeben.
Unter Umständen ist es zur Laufzeit von Interesse, den Typ eines Objekts zu erfahren. Jede .NET-Klasse beerbt die Klasse Object, sowohl die in der Klassenbibliothek vordefinierten als auch die benutzerdefinierten Klassen. Durch die Implementierungsvererbung veröffentlicht jede Klasse die von Object bereitgestellten Methoden. Eine dieser Methoden, die als Ergebnis des Aufrufs standardmäßig den voll qualifizierenden Namen der Klasse liefert, lautet ToString. Ein voll qualifizierender Name setzt sich aus der Angabe des Namespaces, dem die Klasse verwaltungstechnisch zugeordnet ist, und dem Klassennamen zusammen.
Nehmen wir an, die Klasse Circle sei im Namespace CircleApplication definiert. Der Aufruf von ToString mit
| Circle meinKreis = new Circle(); |
| Console.WriteLine(meinKreis.ToString()); |
würde dann zu folgender Ausgabe an der Konsole führen:
| CircleApplication.Circle |
Allerdings dürfen Sie nicht unbedingt erwarten, grundsätzlich immer ein Ergebnis, bestehend aus der Angabe des Namespace und des Typs, zu erhalten. Viele Klasse überschreiben die Methode ToString, d. h., in der Klasse wird die Standardimplementierung an typspezifische Anforderungen angepasst.
Nehmen wir an, eine Variable wäre wie folgt deklariert:
| object myObj; |
Da wir es hier mit einem allgemein gültigen Typ zu tun haben, kann man davon ausgehen, dass sich erst während der Ausführung des Programms der tatsächliche Typ der Variablen myObj aus den Laufzeitbedingungen heraus ergeben wird. Nehmen wir weiter an, es würde sich dabei um den Typ ClassA oder ClassB handeln. Also wird myObj zu einem späteren Zeit entweder mit
| myObj = new ClassA(); |
oder mit
| myObj = new ClassB(); |
initialisiert. Beide Klassen zeichnen sich durch individuelle Methoden aus. Beispielsweise könnte in der Klasse ClassA die Methode Show definiert sein, in der Klasse ClassB die Methode Hide. Sie können sich sicherlich vorstellen, dass je nach Typ, der von myObj beschrieben wird, entweder die Show- oder die Hide-Methode aufgerufen werden muss. Die Frage, die sich stellt, lautet daher: Wie kann ich den Typ, der sich hinter einer Objektreferenz verbirgt, feststellen?
C# bietet uns zur Beantwortung den is-Operator an, der den Laufzeittyp eines Objekts mit einem angegebenen Typ überprüft. Die Syntax des Operators lautet folgendermaßen:
| // Syntax des is-Operators |
| Ausdruck is Typ |
Der Vergleich zwischen Ausdruck und Typ liefert true, wenn der Ausdruck, also die Objektvariable, in den rechts von is stehenden Typ umgewandelt werden kann. Damit ist dieser Operator dazu prädestiniert, in einer if-Anweisung eingesetzt zu werden:
| if(myObj is ClassA) |
| Console.WriteLine("Typ: ClassA"); |
| else |
| Console.WriteLine("Typ: ClassB"); |
Einer Einschränkung unterliegt der is-Operator allerdings. Sehen wir uns dazu das folgende Codefragment an:
| object myObj; |
| myObj = new ClassA(); |
| myObj = null; |
| if(myObj is ClassA) |
| Console.WriteLine("Typ: ClassA"); |
| else |
| Console.WriteLine("Typ: ClassB"); |
Die Variable myObj ist vom Typ object deklariert. Ihr wird in der zweiten Codezeile die Referenz auf ein Objekt vom Typ ClassA übergeben. Anschließend geben wir die Referenz durch die Zuweisung von null wieder frei. Es stellt sich nun die Frage, wie das System zur Laufzeit damit umgeht. Erstaunlicherweise lautet die Konsolenausgabe:
| Typ: ClassB |
Ist der zu untersuchende Ausdruck null, lautet der Rückgabewert nämlich false, aber da wir mit dem else-Zweig alle anderen Bedingungen gleichermaßen behandeln, kommt es zu einer falschen Annahme. Im weiteren Verlauf eines Programms könnte das zu einem Laufzeitfehler und zum unplanmäßigen Beenden des Programms führen, wenn hinter dem else-Zweig nicht nur eine einfache Konsolenausgabe, sondern der Aufruf einer Methode des ClassB-Objekts programmiert ist. Wenn man sich hinsichtlich des Zustands einer Objektvariablen nicht sicher ist, sollte dieser daher zuerst geprüft werden:
| if(myObj != null) |
| { |
| if(myObj is ClassA) |
| Console.WriteLine("Typ: ClassA"); |
| else |
| Console.WriteLine("Typ: ClassB"); |
| } |
| else |
| Console.WriteLine("Objektreferenz ungültig."); |
Wir sollten uns die folgende wichtige Regel merken:
| Wird mit dem is-Operator eine Typüberprüfung auf eine Referenz vorgenommen, die null ist, lautet der Rückgabewert false. |
Wird über die Gleichheit von Objekten gesprochen, gilt es, eine ganz wichtige Frage zu klären: Sind zwei Objekte als »gleich« anzusehen, wenn sie dieselben Eigenschaftswerte aufweisen, oder gelten Objekte dann als »gleich«, wenn zwei Referenzen auf dasselbe konkrete Objekt verweisen? An einem anschaulichen Beispiel hinterfragt: Sind die beiden Objekte obj1 und obj2 vom Typ der Klasse Circle des folgenden Codefragments gleich?
| class MyClass { |
| static void Main(string[] args) { |
| Circle obj1 = new Circle(); |
| Circle obj2 = new Circle(); |
| obj1.Radius = 22; |
| obj2.Radius = 22; |
| } |
| } |
Offensichtlich handelt es sich hierbei um zwei verschiedene Objekte, die jeweils über eine eigene Referenz angesprochen werden. Es ist festzuhalten, dass die Eigenschaft Radius in beiden Objekten denselben Inhalt hat, nämlich 22. Gelten nun die beiden Objekte als »gleich«?
In der Programmierung werden die Begrifflichkeiten genau unterschieden: Zwei Objektreferenzen gelten als identisch, wenn sie auf zwei verschiedene Objekte verweisen, deren Zustand – also die Werte der Eigenschaften – gleich ist, während man von referenzieller Gleichheit spricht, wenn zwei Referenzen auf dieselbe Speicheradresse zeigen. Demnach beschreiben die beiden Objektvariablen obj1 und obj2 identische, jedoch nicht gleiche Objekte.
Die beiden folgenden Codezeilen zeigen den Fall referenzieller Gleichheit.
| Circle obj1 = new Circle(); |
| Circle obj2 = obj1; |

Hier klicken, um das Bild zu vergrößern
Abbildung 4.12 Gleichheit von Objektreferenzen
Um festzustellen, ob zwei Objektreferenzen auf dasselbe konkrete Objekt zeigen, bieten sich drei Möglichkeiten an:
| Die statische Methode ReferenceEquals der Klasse Object. |
| Die Methode Equals. Da diese Methode in der Klasse Object definiert ist und ausnahmslos jede Klasse der .NET-Klassenbibliothek aus dieser abgeleitet wird, kann Equals auf jeden beliebigen Typ aufgerufen werden. |
| Der Vergleichsoperator »==« |
| Achtung An dieser Stelle ist bereits eine Begriffsklärung notwendig: Was ist eine statische Methode? In den meisten Fällen wird eine Klasse definiert, um daraus ein konkretes Objekt zu erzeugen. Der Zugriff auf die Eigenschaften und Methoden setzt dann in jedem Fall eine Objektreferenz voraus. Beabsichtigen Sie allerdings, eine allgemein gültige Funktionssammlung bereitzustellen, ist es nicht unbedingt notwendig, die Elementfunktionen auf ein konkretes Objekt aufzurufen. Denken Sie beispielsweise an die beiden Methoden WriteLine und ReadLine der Klasse Console. Sie rufen diese Methoden auf, ohne dass Sie vorher eine Instanz der Klasse Console angefordert haben. Methoden, die nicht von der Existenz eines konkreten Objekts abhängen, werden im objektorientierten Sprachgebrauch als statische Methoden bezeichnet. Kennzeichnend für den Aufruf statischer Methoden ist die Voranstellung des Klassennamens anstelle einer Objektreferenz, beispielsweise: Console.WriteLine(...); Math.Cos(...); |
Alle drei Möglichkeiten – ReferenceEquals, Equals und »==« – sind zunächst grundsätzlich nur auf Referenztypen anzuwenden, unterscheiden sich untereinander jedoch in wichtigen Details.
Ohne Einschränkungen ist die Methode ReferenceEquals einsetzbar, die beim Aufruf zwei Objektreferenzen entgegennimmt und einen booleschen Wert als Ergebnis des Aufrufs liefert:
| bool bol = Object.ReferenceEquals(obj1, obj2); |
Die Methode ist statisch, wird daher auf den Typnamen aufgerufen und liefert true, wenn beide Verweise dasselbe Objekt im Speicher referenzieren. Andernfalls ist der Rückgabewert false.
Im folgenden Codebeispiel wird der Einsatz dieser Methode demonstriert. Dazu werden insgesamt drei Referenzen des Typs Circle deklariert: obj1, obj2 und obj3. obj1 und obj3 verweisen auf dieselbe Speicheradresse, referenzieren also dasselbe konkrete Objekt. Auf ein zweites Objekt wird über obj2 verwiesen, das – um dem Sachverhalt noch ein wenig mehr Würze zu geben – zustandsgleich mit dem ersten Objekt ist.
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 4\ReferenceEquals |
| // -------------------------------------------------------------- |
| class Program { |
| static void Main(string[] args) { |
| Circle obj1 = new Circle(); |
| Circle obj2 = new Circle(); |
| // der Referenz obj3 wird die Referenz auf das Objekt obj1 zugewiesen |
| Circle obj3 = obj1; |
| // der Zustand der Objekte obj1 und obj2 ist identisch |
| obj1.Radius = 4711; |
| obj2.Radius = 4711; |
| // die Referenzen obj1 und obj2 vergleichen |
| Console.Write("Die Objekte obj1 und obj2 sind "); |
| bool bol = Object.ReferenceEquals(obj1, obj2); |
| if(bol) |
| Console.WriteLine("gleich"); |
| else |
| Console.WriteLine("ungleich"); |
| // die Referenzen obj1 und obj3 vergleichen |
| Console.Write("Die Objekte obj1 und obj3 sind "); |
| bol = Object.ReferenceEquals(obj1, obj3); |
| if(bol) |
| Console.WriteLine("gleich"); |
| else |
| Console.WriteLine("ungleich"); |
| Console.ReadLine(); |
| } |
| } |
| class Circle { |
| public int Radius = 0; |
| } |
Erwartungsgemäß wird der erste Aufruf zu der Feststellung führen, dass zwei verschiedene Speicheradressen von obj1 und obj2 beschrieben werden, während der zweite Aufruf die referenzielle Gleichheit zwischen obj1 und obj3 feststellt.
| Die Objekte obj1 und obj2 sind ungleich |
| Die Objekte obj1 und obj3 sind gleich |
Wir ändern nun den Code und rufen anstelle der statischen Methode ReferenceEquals die Methode Equals eines Objekts auf. Equals ist zwar in der Klasse Circle nicht explizit definiert, wird aber aus der Klasse Object geerbt, die bekanntermaßen die Basisklasse jeder .NET-Klasse ist.
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 4\Equals |
| // -------------------------------------------------------------- |
| class Program { |
| static void Main(string[] args) { |
| ... |
| // die Referenzen obj1 und obj2 vergleichen |
| Console.Write("Die Objekte obj1 und obj2 sind "); |
| if(obj1.Equals(obj2)) |
| Console.WriteLine("gleich"); |
| else |
| Console.WriteLine("ungleich"); |
| // die Referenzen obj1 und obj3 vergleichen |
| Console.Write("Die Objekte obj1 und obj3 sind "); |
| if(obj1.Equals(obj3)) |
| Console.WriteLine("gleich"); |
| else |
| Console.WriteLine("ungleich"); |
| Console.ReadLine(); |
| } |
| } |
| ... |
Das an der Konsole angezeigte Ergebnis ist natürlich dasselbe wie beim Aufruf der Methode ReferenceEquals.
Von der Methode Equals gibt es sowohl eine statische als auch eine objektgebundene Definition. Im Codefragment oben wurde die letztere mit
| obj1.Equals(obj2); |
aufgerufen. Gleichwertig können Sie auch die statische Definition mit folgender Anweisung benutzen:
| Object.Equals(obj1, obj2); |
Beiden Methodenaufrufen liefern true an den Aufrufer zurück, wenn die Objektvariablen auf dasselbe Objekt zeigen.
Bisher haben Sie nur gesehen, dass das Ergebnis sowohl des Aufrufs der Methode ReferenceEquals als auch der Methode Equals gleich ist. Nun stellt sich natürlich sofort die Frage, warum in der Klasse Object zwei anscheinend doch funktionell identische Methoden definiert sind.
Die Antwort darauf ist sehr einfach und hängt mit der Vererbung und den damit verbundenen Möglichkeiten einer Klassendefinition zusammen. Die ursprüngliche Funktionalität der Methode Equals kann von einer abgeleiteten Klasse überschrieben werden. Eine Methode zu überschreiben bedeutet, in einer abgeleiteten Klasse eine gleichnamige Methode zu definieren, die jedoch eine andere Codeimplementierung aufweist. Überschrieben wird eine geerbte Methode, um diese beispielsweise an die individuellen Anforderungen einer Klasse anzupassen. Equals kann von jeder Klasse überschrieben werden, und somit haben Sie auch keine Garantie, dass die Methode immer so funktioniert, wie Sie es oben gesehen haben. Im Zweifelsfall müssen Sie die Dokumentation der entsprechenden Klasse zur Hand nehmen. Das erinnert uns an die weiter oben beschriebene Methode ToString, die ebenfalls typspezifisch überschrieben werden darf.
Wenden wir uns nun noch abschließend kurz dem Vergleichsoperator »==« zu, der nichts anderes darstellt als eine zusätzliche sprachliche Spielart von C#.
| if(obj1 == obj2) |
| Console.WriteLine("gleich"); |
| else |
| Console.WriteLine("ungleich"); |
Das Ergebnis des Prozeduraufrufs unterscheidet sich nicht von dem der Methode ReferenceEquals bzw. der nicht überschriebenen Methode Equals.
Es gibt einen Sonderfall, der aus dem Rahmen unserer bisherigen Ausnahmen fällt und deshalb auch einer gesonderten Betrachtung unterzogen werden muss: Zeichenfolgen vom Datentyp string. Sehen Sie sich dazu das folgende Beispiel an:
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 4\Stringvergleich |
| // -------------------------------------------------------------- |
| class Program { |
| static void Main(string[] args) { |
| string str1 = "C# ist spitze!"; |
| string str2 = "C# ist spitze!"; |
| if (str1 == str2) |
| Console.WriteLine("Beide Strings sind gleich"); |
| else |
| Console.WriteLine("Die Strings sind ungleich"); |
| Console.ReadLine(); |
| } |
| } |
Es sind zwei Objektvariablen vom Typ string deklariert, die denselben Inhalt haben. Man könnte im ersten Moment erwarten, dass in den else-Zweig der bedingten Anweisung gesprungen wird. Dem ist aber nicht so, denn tatsächlich wird an der Konsole
| Beide Strings sind gleich |
ausgegeben. Wie kann dieses Phänomen erklärt werden?
String-Variablen unterscheiden sich von den Variablen anderer Typen: Der Inhalt kann, wenn er einmal festgelegt ist, nicht verändert werden. Die folgenden beiden Codezeilen sollen das erklären:
| string str = "Hallo Welt"; |
| str = "Hallo Welt und guten Morgen"; |
In der ersten Zeile wird die Variable str deklariert und initialisiert, in der zweiten Zeile wird der Variablen ein neuer Wert zugewiesen. Tatsächlich wird aber der alte Inhalt nicht durch einen anderen ersetzt, sondern eine neue Speicheradresse reserviert, der Zeiger von der alten Adresse mit dem Inhalt »Hallo Welt« auf die neue Adresse umgebogen und der neue Inhalt »Hallo Welt und guten Morgen« hineingeschrieben.
Definitiv arbeitet dieser Code also tatsächlich mit zwei unterschiedlichen Speicheradressen – der Anwender bemerkt davon jedoch nichts. Dieses Verhalten scheint nicht sehr effizient zu sein, denn das Verbiegen eines Zeigers kostet zweifelsfrei Systemleistung. Trotzdem gibt es einen Vorteil, denn unveränderliche Zeichenfolgen sind gleichzeitig auch für die gemeinsame Nutzung eingerichtet. Das ist im obigen Beispiel Stringvvergleich der Fall und wird durch das Ausgabeergebnis an der Konsole auch bewiesen: str1 und str2 verweisen auf dieselbe Speicheradresse, weil sie denselben Inhalt beschreiben.
Manchmal ist es erforderlich, ein zusätzliches Objekt als exaktes Abbild eines bereits existierenden zu erstellen. Dabei soll der Anfangszustand der Kopie exakt dem Zustand des zu kopierenden Objekts entsprechen.
Die Klasse Object bietet zu diesem Zweck die Methode MemberwiseClone an, die als Rückgabewert die Referenz auf das neue Objekt liefert. Diese Methode ist wie folgt definiert:
| protected Object MemberwiseClone(); |
Der Zugriffsmodifizierer protected wird erst im Rahmen der Vererbung Bedeutung erlangen. Er ähnelt sehr dem Modifizierer private. Solchermaßen deklarierte Member werden nicht veröffentlicht und können daher nicht von außerhalb der Klasse aufgerufen werden. Der Zugriff ist nur aus der aktuellen Klasse heraus oder aus einer abgeleiteten möglich. Dazu später mehr.
MemberwiseClone ist eine Methode, die jede .NET-Klasse von Object erbt. Da die Methode nicht statisch definiert ist, wird sie auf einem konkreten Objekt aufgerufen – im folgenden Codefragment als Anweisung in der Methode GetClone.
| class Circle { |
| public int Radius = 0; |
| public Circle GetClone() { |
| return (Circle)this.MemberwiseClone(); |
| } |
| } |
MemberwiseClone ist vom Typ Object und muss zum Schluss in den Typ Circle konvertiert werden.
Nun wollen wir noch ein Beispielprogramm entwickeln, um uns vom Klonen eines Objekts zu überzeugen.
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 4\ObjectClonen |
| // -------------------------------------------------------------- |
| class Program { |
| static void Main(string[] args) { |
| Circle obj = new Circle(); |
| obj.Radius = 85; |
| // das Objekt obj clonen |
| Circle clonObj = obj.GetClone(); |
| // Referenzvergleich |
| if (obj == clonObj) |
| Console.WriteLine("Identische Objekte"); |
| else |
| Console.WriteLine("Verschiedene Objekte"); |
| Console.Write("TestProp der Clone = {0}", clonObj.Radius); |
| Console.ReadLine(); |
| } |
| } |
An der Konsole wird nach der Feststellung der Ungleichheit der beiden Referenzen der Inhalt der Eigenschaft Radius des geklonten Objekts ausgegeben: Tatsächlich sind die Inhalte gleich.
Eine Schwierigkeit verbirgt sich aber dennoch hinter dem Klonen: Beim Kopiervorgang kann nur bitweise kopiert werden. Hinsichtlich der Felder, die durch Werttypen beschrieben werden (z.B. int oder float), funktioniert das tadellos. Wenn ein Feld jedoch ein Referenztyp ist, wie im folgenden Code das Feld Turbine der Klasse Flugzeug, wird der Inhalt dieses Feldes nur bitweise kopiert.
| class Flugzeug { |
| public int Spannweite; |
| public Triebwerk Turbine = new Triebwerk(); |
| } |
Das bitweise Kopieren eines auf einem Referenztyp basierenden Feldes hat eine wichtige Konsequenz: Da das Feld Turbine die Startadresse des referenzierten Objekts enthält, wird diese 1:1 in das geklonte Objekt kopiert. Die Schlussfolgerung daraus kann nur lauten, dass die vom Ursprungsobjekt referenzierten Objekte nicht geklont werden, denn sowohl das Feld des Originals als auch das der Klone verweisen auf dasselbe Objekt.
Diesen Sachverhalt wollen wir durch ein Beispiel verdeutlichen.
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 4\ObjektClonen2 |
| // -------------------------------------------------------------- |
| class Program { |
| static void Main(string[] args) { |
| ClassA obj = new ClassA(); |
| obj.TestProp = 85; |
| // das Objekt obj clonen |
| ClassA clonObj = obj.GetClone(); |
| // Referenzvergleich |
| if (obj == clonObj) |
| Console.WriteLine("Identische Objekte"); |
| else |
| Console.WriteLine("Verschiedene Objekte"); |
| // Referenzvergleich der Felder |
| if(obj.MyOwnObj == clonObj.MyOwnObj) |
| Console.WriteLine("Gleiche Feldreferenzen"); |
| else |
| Console.WriteLine("Verschiedene Feldreferenzen"); |
| Console.ReadLine(); |
| } |
| } |
| // ----------- Klasse ClassA ----------- |
| class ClassA { |
| public int TestProp = 0; |
| public ClassB MyOwnObj = new ClassB(); |
| // objekteigene Methode |
| public ClassA GetClone() { |
| return (ClassA)this.MemberwiseClone(); |
| } |
| } |
| // ----------- Klasse ClassB ----------- |
| class ClassB { |
| public int TestPropB = 0; |
| } |
Entscheidend in der Definition der Klasse ClassA ist das neu hinzugekommene Feld MyOwnObj vom Typ ClassB. Mit der Instanziierung der Klasse ClassA in der Main-Prozedur wird automatisch von dieser Klasse auch ein Objekt des Typs ClassB erzeugt und die Referenz dem objekteigenen Feld MyOwnObj zugewiesen. Diese Referenz wird durch die Methode MemberwiseClone bitweise kopiert, was die Ausgabe
| Gleiche Feldreferenzen |
an der Konsole beweist.
a| Achtung Ist es erforderlich, auch die von einer Klasse referenzierten Objekte zu klonen, sollte die Klasse um die Implementierung der Schnittstelle ICloneable und deren Methode Clone erweitert werden. Das Thema der Schnittstellenimplementierung führt aber an dieser Stelle deutlich zu weit und wird in Kapitel 6 noch einmal aufgegriffen. |
| Um den Typ eines Objekts festzustellen, bietet sich die Methode ToString der Klasse Object an. ToString liefert standardmäßig den voll qualifizierenden Namen der Klasse, kann aber von einer abgeleiteten Klasse überschrieben werden. |
| Mit dem objektspezifischen Vergleichsoperator is lässt sich der Typ einer Klasse in einer bedingten Anweisung überprüfen. Werttypen sind von dieser Überprüfung ausgeschlossen. |
| Die Klasse Object bietet mit ihrer Methode ReferenceEquals die Möglichkeit festzustellen, ob zwei Referenzen identisch sind, also auf dasselbe Objekt im Speicher verweisen. |
| Ein Referenzvergleich kann auch mit der Methode Equals, ebenfalls ein Mitglied der Klasse Object, vorgenommen werden. Allerdings überschreiben viele Klassen diese Methode, was zu unvorhersehbaren Ergebnissen führen könnte. |
| Eine dritte Variante des Referenzvergleichs bietet der Operator »==«, der auf zwei Operanden vom Typ einer Referenz arbeitet. |
| Für Variablen vom Typ string gelten besondere Bedingungen. Daher ist der Referenzvergleich von Zeichenfolgen ungeeignet. |
| Mit der Methode MemberwiseClone lässt sich eine exakte Kopie von Objekten erzeugen. Intern referenzierte dritte Objekte werden dabei nicht geklont. |
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.